package isn99

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"lottery/conf"
	"lottery/constant"
	"lottery/models"
	"lottery/utils"

	//"lottery/services/lottery"

	"github.com/astaxie/beego/logs"
)

type Isn99 struct {
	userAgent string
	agentInfo *models.Agent
	jar       *cookiejar.Jar
	client    *http.Client
	userName  string // isn99 username
	xfp       string

	wg           *sync.WaitGroup
	stoped       chan struct{}
	gameChan     chan []*models.Game
	gameDataChan chan []*models.GameData
}

// 下单
// 监测登录
// 获取当前赛事
// 获取实时数据

func (isn *Isn99) Platform() int {
	return constant.ISN99
}

func (isn *Isn99) AgentInfo() *models.Agent {
	return isn.agentInfo
}

func (isn *Isn99) IsStop() bool {
	select {
	case <-isn.stoped:
		return true
	default:
		return false
	}
}

func (isn *Isn99) switchIP(ip string) {
	if ip == "" {
		return
	}

	targetIP := isnDNSRing.GetIp()
	if targetIP == "" {
		return
	}

	isn.client.Transport = utils.GetTransport(ip, targetIP)
}

func (isn *Isn99) getIP() string {
	if IPsRing == nil {
		return ""
	}

	return IPsRing.GetIp()
}

func (isn *Isn99) errActionResp(err error) (resp *models.ActionResp) {
	resp = new(models.ActionResp)
	if err != nil {
		resp.IsSuccess = false
		resp.Msg = err.Error()
		return
	}

	resp.IsSuccess = true
	return
}

func (isn *Isn99) initLoginEnv() (err error) {
	isnUrl := "http://www.isn99.com"
	_, cookies, err := utils.GetCookieWithHtml(isnUrl)
	if err != nil {
		return
	}

	domain, _ := url.Parse(isnUrl)
	isn.jar.SetCookies(domain, cookies)
	return
}

func (isn *Isn99) fillXfp() (err error) {
	isnUrl := "http://www.isn99.com/membersite/zh-CN/ui/"
	domain, _ := url.Parse(isnUrl)
	cookies := isn.jar.Cookies(domain)
	cookieData, err := json.Marshal(cookies)
	if err != nil {
		return
	}

	logs.Notice("======%s", string(cookieData))

	// cookies, xfp, err := utils.GetCookieISNXFP(isnUrl, string(cookieData))
	// if err != nil {
	// 	return
	// }

	// isn.jar.SetCookies(domain, cookies)
	// isn.xfp = xfp
	return
}

func (isn *Isn99) getVerifyCodeData() (codeImg string, err error) {
	reqUrl := fmt.Sprintf("http://www.isn99.com/membersite/ui/captcha?w=50&h=20&fs=18&_%d", time.Now().UnixNano()/1000000)
	httpReq, _ := http.NewRequest("GET", reqUrl, nil)
	httpReq.Header = isn.getDefaultHeader()
	resp, err := isn.client.Do(httpReq)
	if err != nil {
		return
	}

	defer resp.Body.Close()

	respData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	codeImg = base64.StdEncoding.EncodeToString(respData)
	return
}

func (isn *Isn99) Login() (actionResp *models.ActionResp) {
	isn.switchIP(isn.getIP())

	if err := isn.initLoginEnv(); err != nil {
		return isn.errActionResp(err)
	}

	var code string
	for k := 0; k < 3; k++ {
		time.Sleep(1 * time.Second)
		codeImg, err := isn.getVerifyCodeData()
		if err != nil {
			return isn.errActionResp(err)
		}

		logs.Debug(codeImg)
		code, err = isn.parseVerifyCode(codeImg)
		if err != nil {
			return isn.errActionResp(err)
		}

		logs.Debug(code)
		if v, err := strconv.Atoi(code); err == nil && v > 1000 {
			break
		} else {
			code = ""
		}
	}

	if code == "" {
		return isn.errActionResp(errors.New("验证码无法识别"))
	}

	params := make(url.Values)
	params.Set("language", "zh_CN")
	params.Set("username", isn.agentInfo.LoginName)
	params.Set("password", isn.agentInfo.Passwd)
	params.Set("code", code)
	params.Set("buId", "1")
	params.Set("dummyLang", "")

	loginUrl := "http://www.isn99.com/membersite/resource/member/login"
	httpReq, _ := http.NewRequest("POST", loginUrl, strings.NewReader(params.Encode()))

	header := isn.getDefaultHeader()
	header.Set("Referer", "http://www.isn99.com/membersite/zh-CN/login.jsp")
	httpReq.Header = header

	httpResp, err := isn.client.Do(httpReq)
	if err != nil {
		return isn.errActionResp(err)
	}

	loginData, err := ioutil.ReadAll(httpResp.Body)
	if err != nil {
		return isn.errActionResp(err)
	}
	httpResp.Body.Close()

	if err := isn.setAgentInfo(loginData); err != nil {
		logs.Debug("登录失败:%s", string(loginData))
		return &models.ActionResp{IsSuccess: false}
	}

	// if err = isn.fillXfp(); err != nil {
	// 	logs.Debug("登录失败，无法获取XFP")
	// 	return isn.errActionResp(err)
	// }
	return &models.ActionResp{IsSuccess: true}
}

func (isn *Isn99) setAgentInfo(data []byte) (err error) {
	loginResp := new(LoginResp)
	if err = json.Unmarshal(data, loginResp); err != nil {
		return
	}

	isn.agentInfo.Balance = int(loginResp.BetCredit * 100)
	isn.userName = strconv.Itoa(loginResp.MemberID)
	logs.Debug("%+v", isn.agentInfo)
	return
}

// 验证码解析
func (isn *Isn99) parseVerifyCode(data string) (code string, err error) {
	verifyCodeUrl := conf.LoadResource().VerifyCodeUrl
	body := fmt.Sprintf("{\"img_base64\":\"%s\",\"engine\":\"tesseract\"}", data)
	resp, err := http.Post(verifyCodeUrl, "application/json", strings.NewReader(body))
	if err != nil {
		return
	}
	defer resp.Body.Close()

	tmpData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	code = strings.TrimSpace(string(tmpData))
	// fmt.Scanf("%s", &code)
	code = strings.Replace(code, " ", "", -1)
	return
}

func (isn *Isn99) ReLogin() *models.ActionResp {
	// isn.agentInfo.TickReset()
	return isn.Login()
}

// team 1 主场  2 客场
func (isn *Isn99) Place(price int, team int, tickData interface{}, gameData *models.GameData, switchIp bool) (actionResp *models.ActionResp) {
	if switchIp {
		isn.switchIP(isn.getIP())
	}

	actionResp = new(models.ActionResp)
	tick, ok := tickData.(*TickResp)
	if !ok {
		logs.Warn("tick data:%+v", tick)
		actionResp.IsSuccess = false
		actionResp.Msg = "数据处理失败，无效下单"
		return
	}

	orderUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/bp/preparebet?ts=%d", utils.CurrentMills())
	form := make(url.Values)
	form.Set("selectionId", strconv.Itoa(tick.SelectionID))
	form.Set("odds", utils.TrimLastZero(fmt.Sprintf("%0.2f", tick.DecimalOdds)))
	form.Set("nativeOdds", tick.AccumulativeOdds)
	form.Set("oddsFormat", utils.TrimLastZero(fmt.Sprintf("%0.2f", tick.OddsFormat)))
	form.Set("handicap", utils.TrimLastZero(fmt.Sprintf("%0.2f", tick.Handicap)))
	form.Set("score", tick.Score)
	form.Set("eventPitcherId", strconv.Itoa(tick.EventPitcherID))
	form.Set("stake", strconv.Itoa(price/100))
	form.Set("username", isn.userName)
	// if !isFirst {
	// form.Set("acceptBetterOdds", "true")
	// }

	orderReq, _ := http.NewRequest("POST", orderUrl, strings.NewReader(form.Encode()))
	orderReq.Header = isn.getDefaultHeader()
	orderReq.Header = isn.simulationHeader(orderReq.Header)
	orderReq.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
	orderReq.Header.Set("Referer", "http://www.isn99.com/membersite/zh-CN/ui/")
	orderResp, err := isn.client.Do(orderReq)
	if err != nil {
		logs.Warn("isn client do order req err %s", err.Error())
		return isn.errActionResp(err)
	}

	data, err := ioutil.ReadAll(orderResp.Body)
	orderResp.Body.Close()
	if err != nil {
		logs.Warn("isn read all data err %s", err.Error())
		return isn.errActionResp(err)
	}

	order := new(OrderResp)
	if err := json.Unmarshal(data, order); err != nil {
		logs.Warn("isn unmarshal err %s", err.Error())
		return isn.errActionResp(err)
	}

	if order.RespCode != 0 {
		logs.Notice("下注失败 tick:%+v ", tick)
		// 赔率已更改
		if order.ResponseMsgCode == "MT004" && order.NewNativeOdds != "" {
			newOdds, err := strconv.ParseFloat(order.NewNativeOdds, 64)
			if err == nil {
				tick.DecimalOdds = newOdds
				tick.AccumulativeOdds = order.NewOdds
				actionResp.ExtData = tick
			}
		}
		logs.Warn("isn resp data:%s tick:%+v", string(data), actionResp.ExtData)
		actionResp.IsSuccess = false
		actionResp.Msg = order.RespMessage
		return actionResp
	}

	actionResp.IsSuccess = true
	return actionResp

}

func (isn *Isn99) CheckLogin() (actionResp *models.ActionResp) {
	actionResp = new(models.ActionResp)
	reqUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/member?_=%d", utils.CurrentMills())
	req, _ := http.NewRequest("GET", reqUrl, nil)
	req.Header = isn.getDefaultHeader()
	req.Header = isn.simulationHeader(req.Header)

	resp, err := isn.client.Do(req)
	if err != nil {
		return isn.errActionResp(err)
	}

	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return isn.errActionResp(err)
	}

	memberResp := new(MemberResp)
	if err := json.Unmarshal(data, memberResp); err != nil {
		logs.Warn("unmarshal member resp %d", len(data))
		return isn.errActionResp(err)
	}

	// 更新余额
	isn.agentInfo.Balance = int(memberResp.BetCredit * 100)

	///////////////////////
	reqUrl = fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/bp/pendingAndLastTenBets?_=%d", utils.CurrentMills())
	req, _ = http.NewRequest("GET", reqUrl, nil)
	req.Header = isn.getDefaultHeader()
	req.Header = isn.simulationHeader(req.Header)

	resp, err = isn.client.Do(req)
	if err != nil {
		return isn.errActionResp(err)
	}
	resp.Body.Close()

	///////////////////////
	reqUrl = fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/events/time?_=%d", utils.CurrentMills())
	req, _ = http.NewRequest("GET", reqUrl, nil)
	req.Header = isn.getDefaultHeader()
	req.Header = isn.simulationHeader(req.Header)

	resp, err = isn.client.Do(req)
	if err != nil {
		return isn.errActionResp(err)
	}
	resp.Body.Close()

	///////////////////////
	reqUrl = fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/announcement?_=%d", utils.CurrentMills())
	req, _ = http.NewRequest("GET", reqUrl, nil)
	req.Header = isn.getDefaultHeader()
	req.Header = isn.simulationHeader(req.Header)

	resp, err = isn.client.Do(req)
	if err != nil {
		return isn.errActionResp(err)
	}
	resp.Body.Close()

	actionResp.IsSuccess = true
	return
}

func (isn *Isn99) Run() {
	isn.wg.Add(1)
	defer isn.wg.Done()
	flushTime := time.Duration(conf.LoadResource().ISN99Refresh) * time.Millisecond

	tick := time.NewTicker(flushTime)
	for {
		select {
		case _ = <-tick.C:
			start := time.Now()
			gameDataResps, err := isn.crawlGames()
			if err != nil {
				logs.Warn("crawl games %s", err.Error())
				continue
			}

			if len(gameDataResps) == 0 {
				continue
			}

			games, gameDatas, err := isn.parseGameData(gameDataResps)
			if err != nil {
				logs.Warn("parseGameData %s", err.Error())
				continue
			}

			end := time.Now()
			logs.Debug("isn99 getGameData use time:%d mill sec", (end.UnixNano()-start.UnixNano())/int64(time.Millisecond))

			isn.gameChan <- games
			isn.gameDataChan <- gameDatas
		case _ = <-isn.stoped:
			close(isn.gameChan)
			close(isn.gameDataChan)
			return
		}
	}
}

//
func (isn *Isn99) Tick(team int, gameData *models.GameData) (rate float64, tickData interface{}, discard bool, err error) {
	// isn.agentInfo.TickInc()

	selId := gameData.HOrderId
	rate = gameData.HRate
	if team == 2 {
		selId = gameData.AOrderId
		rate = gameData.ARate
	}

	rateUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/bp/singlebetlimit/%s/3/4?_=%d", selId, utils.CurrentMills())
	rateReq, _ := http.NewRequest("GET", rateUrl, nil)
	rateReq.Header = isn.getDefaultHeader()
	rateReq.Header = isn.simulationHeader(rateReq.Header)
	rateResp, err := isn.client.Do(rateReq)
	if err != nil {
		return
	}

	data, err := ioutil.ReadAll(rateResp.Body)
	rateResp.Body.Close()
	if err != nil {
		return
	}

	tickBet := new(TickBetResp)
	if err = json.Unmarshal(data, tickBet); err != nil {
		logs.Warn("unmarshal tickbet resp err:%s", err.Error())
		return
	}

	gameData.LowPrice = tickBet.MinBet * 100
	gameData.HighPrice = tickBet.MaxBet * 100

	tickUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/bp/singlebetticket/%s/3/4?_=%d", selId, utils.CurrentMills())
	tickReq, _ := http.NewRequest("GET", tickUrl, nil)
	tickReq.Header = isn.getDefaultHeader()
	tickReq.Header = isn.simulationHeader(tickReq.Header)
	tickResp, err := isn.client.Do(tickReq)
	if err != nil {
		return
	}

	data, err = ioutil.ReadAll(tickResp.Body)
	tickResp.Body.Close()
	if err != nil {
		return
	}

	tick := new(TickResp)
	if err = json.Unmarshal(data, tick); err != nil {
		if len(data) < 4096 {
			logs.Warn("unmarshal tick resp:%s err:%s", string(data), err.Error())
		} else {
			logs.Warn("unmarshal tick resp too lage err:%s", err.Error())
		}
		return
	}

	newRate := tick.Odds
	if rate != newRate {
		rate = newRate
	}
	var hscore, ascore int
	if _, err = fmt.Sscanf(tick.Score, "%d-%d", &hscore, &ascore); err != nil {
		return
	}

	gameData.HScore = hscore
	gameData.AScore = ascore
	tickData = tick
	return
}

func (isn *Isn99) GetGames() <-chan []*models.Game {
	return isn.gameChan
}

func (isn *Isn99) GetGameData() <-chan []*models.GameData {
	return isn.gameDataChan
}

func (isn *Isn99) GetOrderInfo(t time.Time) (html string, err error) {
	//http://www.isn99.com/membersite/zh-CN/resource/statement/1/1505966400000?_=1506243420897
	// //
	// h, _ := time.ParseDuration("-1h")
	// h1 := t.Add(8 * h)
	//reqUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/statement/1/%d?_=%d", h1.UnixNano()/int64(time.Millisecond), utils.CurrentMills())
	reqUrl := fmt.Sprintf("http://www.isn99.com/membersite/zh-CN/resource/bp/betList/normal/7/all/-1?_=", utils.CurrentMills())

	//logs.Debug("GetOrderInfo==>", reqUrl)
	req, _ := http.NewRequest("GET", reqUrl, nil)
	req.Header = isn.getDefaultHeader()
	req.Header = isn.simulationHeader(req.Header)
	resp, err := isn.client.Do(req)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	var inter interface{}
	err = json.Unmarshal(data, &inter)
	//html = string(data)
	logs.Debug("抓取到 ISN99 order下注记录信息", string(data))
	if err != nil {
		logs.Debug("json unmarshal ==>", string(data), err)
		return
	}

	js, ok := inter.(map[string]interface{})
	if !ok {
		logs.Debug("js err", ok)
		return
	}

	totalStake, ok := js["totalStake"].(string)
	if !ok {
		err = errors.New("json 解析 取 totalStake 出错")
		return
	}

	totalEstimatePayout, ok := js["totalStake"].(string)
	if !ok {
		err = errors.New("json 解析 取 totalEstimatePayout 出错")
		return
	}
	logs.Debug("totalStake", totalStake, totalEstimatePayout, js)
	// totalBetCount, ok := js["totalBetCount"].(float64)
	// if !ok {
	// 	err = errors.New("json 解析 取 totalEstimatePayout 出错")
	// 	return
	// }

	normalList, ok := js["normalList"].([]interface{})

	if !ok {
		err = errors.New("json 解析 取 normalList 出错")
		return
	}

	header := `<table class="tablecontent">
    <tbody>
        <tr class="td-black-grad">
            <th class="width30">编号</th>
            <th class="width60">时间</th>
            <th class="width180">种类</th>
            <th class="width320">详情</th>
            <th class="width70">注额</th>
            <th class="width70">预计派彩</th>
        </tr>`

	main_tmp := ` <tr class="bg-white">
            <td class="width30 align-left align-top"><span class="black">%.0f</span></td>
            <td class="width60 align-left align-top"><span class="black">%s</span><span class="black">%s</span><span class="blue">编号: %.0f</span></td>
            <td class="width180 align-left align-top"><span class="black">%s</span><span class="black">&nbsp;</span></td>
            <td class="width370 align-left align-top">
                <div class="font-wrap">
                   %s
                </div>
            </td>
            <td class="width70 align-right align-top"><span class="font-black">%s</span></td>
            <td class="width70 align-right align-top"><span class="font-black">%s</span></td>
        </tr>
`
	foot := ` <tr>
            <td colspan="4" class="width640 bg-lgrey"></td>
            <td class="width70 bg-lyellow"><span class="align-right black-bold">%s</span></td>
            <td class="width70 bg-lyellow"><span class="align-right black-bold">%s</span></td>
        </tr>
    </tbody>
</table>`
	foot = fmt.Sprintf(foot, totalStake, totalEstimatePayout)
	main := ""
	for _, item := range normalList {
		normal, ok := item.(map[string]interface{})
		if !ok {
			err = errors.New("json 解析 取 normalList 出错")
			return
		}
		// league, ok := normal["league"].(string)
		// if !ok {
		// 	err = errors.New("json 解析 取 league 出错")
		// 	return
		// }

		htmlWrappedDetails, ok := normal["htmlWrappedDetails"].(string)
		if !ok {
			err = errors.New("json 解析 取 htmlWrappedDetails 出错")
			return
		}

		marketType, ok := normal["marketType"].(string)
		if !ok {
			err = errors.New("json 解析 取 marketType 出错")
			return
		}
		estimatePayoutStr, ok := normal["estimatePayoutStr"].(string)
		if !ok {
			err = errors.New("json 解析 取 estimatePayoutStr 出错")
			return
		}
		stakeStr, ok := normal["stakeStr"].(string)
		if !ok {
			err = errors.New("json 解析 取 stakeStr 出错")
			return
		}

		betId, ok := normal["betId"].(float64)
		if !ok {
			err = errors.New("json 解析 取 betId 出错")
			return
		}
		seqNo, ok := normal["betId"].(float64)
		if !ok {
			err = errors.New("json 解析 取 seqNo 出错")
			return
		}

		betPlacedDate, ok := normal["betPlacedDate"].(float64)
		if !ok {
			err = errors.New("json 解析 取 betPlacedDate 出错")
			return
		}

		time_str := strconv.FormatFloat(betPlacedDate, 'f', -1, 64)
		time_int, err := strconv.ParseInt(time_str, 10, 64)
		if err != nil {
			return "", err
		}
		str_date := time.Unix(time_int/1000, 0).Format("02/01/2006")
		str_hour := time.Unix(time_int/1000, 0).Format("15:04:05 PM")
		//fmt.Println("t", time_int, str_date, "===>", str_hour, err)
		main = main + fmt.Sprintf(main_tmp, seqNo, str_date, str_hour, betId, marketType, htmlWrappedDetails, estimatePayoutStr, stakeStr)
	}
	html = header + main + foot
	return
}

func (isn *Isn99) Destroy() {
	logs.Warn("isn99 destroy ...%d", isn.agentInfo.Id)
	close(isn.stoped)
	isn.wg.Wait()
}

func (isn *Isn99) simulationHeader(header http.Header) http.Header {
	// header.Set("X-fp", isn.xfp)
	header.Set("X-Requested-With", "XMLHttpRequest")
	return header
}

func (isn *Isn99) getDefaultHeader() http.Header {
	header := make(http.Header)
	header.Set("User-Agent", isn.userAgent)
	return header
}
